package com.demon.activitychange.net.impl;

import android.os.Handler;
import android.support.annotation.Nullable;

import com.demon.activitychange.util.JSONObjectPack;
import com.demon.activitychange.util.TraceUtil;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.Timer;
import java.util.TimerTask;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

public class WebSocketMgr {
    private static WebSocketMgr instance;
    private OkHttpClient client = new OkHttpClient();
    private WebSocket mWebSocket;
    private Timer mHeartTimer;
    private Handler mHandler = new Handler();
    private String mIp;
    private IWebSocketRecv mWebSocketRecv = null;
    private boolean isAutoConnect = true;

    public static WebSocketMgr getInstance() {
        if (instance == null) {
            synchronized (WebSocketMgr.class) {
                if (instance == null) {
                    instance = new WebSocketMgr();
                }
            }
        }
        return instance;
    }

    public synchronized void openWebSocket(String ip, final String account, IWebSocketRecv recv) {
        mIp = ip;
        mWebSocketRecv = recv;
        String url = String.format("ws://%s:3001/", mIp) + account;
        Request req = new Request.Builder().get().url(url).build();

        if (mWebSocket != null) {
            return;
        }
        TraceUtil.e("ws registerWebSocket url: " + url);
        client.newWebSocket(req, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                TraceUtil.e("ws onOpen " + account);
                mWebSocket = webSocket;
                setAutoReconnect(true);
                startHeart(account);
                setRecHeartTick();
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                // {"type":"command","cmd":"","data":"3213222","msg":"xxx"}
                // {"type":"heart"}

                try {
                    JSONObject obj = new JSONObject(text);
                    String type = obj.getString("type");
                    if ("command".equals(type)) {
                        TraceUtil.e("ws onMessage: " + text);
                        recvCmd(obj.getString("cmd"), obj.getString("data"));
                    } else if ("heart".equals(type)) {
                        setRecHeartTick();
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                TraceUtil.e("ws onClosing");
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                TraceUtil.e("ws onClosed");
                closeWebSocket();
                stopHeart();
                reconnect(account);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {
                super.onFailure(webSocket, t, response);
                TraceUtil.e("ws onFailure");
                closeWebSocket();
                stopHeart();
                reconnect(account);
            }
        });
    }

    public synchronized void closeWebSocket() {
        if (mWebSocket != null) {
            TraceUtil.e("ws closeWebSocket");
            mWebSocket.cancel();
            mWebSocket = null;
        }
    }

    public synchronized void closeWebSocketNoReconnect() {
        setAutoReconnect(false);
        closeWebSocket();
    }

    public synchronized void restartWebSocket(String ip, final String account, IWebSocketRecv recv) {
        closeWebSocket();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                openWebSocket(ip, account, recv);
            }
        }, 500);

    }

    public void setAutoReconnect(boolean isAutoReconnect) {
        this.isAutoConnect = isAutoReconnect;
    }

    public void sendCmd(String cmd, JSONObject data) {
        if (mWebSocket != null) {
            JSONObject obj = new JSONObjectPack()
                    .putValue("type", "command")
                    .putValue("cmd", cmd)
                    .putValue("data", data)
                    .getJSONObject();
            TraceUtil.e("ws sendCmd: cmd = " + cmd + " , data = " + data);
            mWebSocket.send(obj.toString());
        }
    }

    public void recvCmd(String cmd, String data) {
        TraceUtil.e("ws onMessage: cmd = " + cmd + " , data = " + data);
        if (mWebSocketRecv != null) {
            mWebSocketRecv.recvCmd(cmd, data);
        }
    }

    private void startHeart(final String account) {
        if (mHeartTimer == null) {
            TraceUtil.e("ws startHeart");
            TimerTask task = new TimerTask() {
                public void run() {
                    if (mWebSocket != null) {
                        boolean res = mWebSocket.send("{\"bid\": \"" + account + "\"}");
                        // TraceUtil.d("ws websocket is not null，send result = " + res);

                        long heartTick = getRecHeartTick();
                        long curTick = System.currentTimeMillis();
                        long dist = (curTick - heartTick) / 1000;
                        if (dist > 12) {
                            TraceUtil.e("ws websocket rec heart，dist = " + dist);
                            closeWebSocket();
                        }
                    } else {
                        TraceUtil.e("ws websocket is null，close heart");
                    }
                }
            };
            mHeartTimer = new Timer();
            mHeartTimer.schedule(task, 1000, 5000);
        }
    }

    private void stopHeart() {
        if (mHeartTimer != null) {
            TraceUtil.e("ws stopHeart");
            try {
                mHeartTimer.cancel();
                mHeartTimer = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void reconnect(final String account) {
        if (!isAutoConnect) {
            return;
        }
        TraceUtil.e("ws reconnect wait");
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                openWebSocket(mIp, account, mWebSocketRecv);
            }
        }, 5000);
    }

    private volatile long lastTick = 0;

    private void setRecHeartTick() {
        lastTick = System.currentTimeMillis();
    }

    private long getRecHeartTick() {
        return lastTick;
    }
}

